#include <sys/types.h>
#include <sys/stat.h>
#include <sys/unistd.h>

#include <mythtv/mythcontext.h>
#include <mythtv/mythplugin.h>
#include <mythtv/mythmedia.h>
#include <mythtv/mythdbcon.h>

#include <qapplication.h>
#include <qstring.h>
#include <qdir.h>

extern "C"
{
   #include "md5.h"
}
#include "ScanForAudio.h"
#include "metadata.h"
#include "decoder.h"

extern QString m_MetaUnknown;

ScanForAudio::ScanForAudio()
{
   printf( "ScanForAudio::ScanForAudio()\n" );
   MaxCount = 0;
   Count = 0;
}

ScanForAudio::~ScanForAudio()
{
   printf( "ScanForAudio::~ScanForAudio()\n" );
}

void ScanForAudio::SetHandler( EventInterface* handler )
{
   Handler = handler;
}

void ScanForAudio::ClearHandler()
{
   m_Mutex.lock();
   Handler = 0;
   m_Mutex.unlock();
}

void ScanForAudio::Start()
{
   printf( "ScanForAudio::Start()\n" );
   start();
}

void ScanForAudio::Scan()
{
   int count;

   RootPath = gContext->GetSetting("MythAudio.Store");
   RootPath = QDir::cleanDirPath( RootPath );
   if( !RootPath.endsWith("/") )
   {
      RootPath += "/";
   }

   CreateDatabase();

   printf( "counting...\n" );
   count = CountFiles( RootPath );
   printf( "count %d\n", count );

   printf( "Buiding File List\n" );
   BuildFileList( RootPath );
   printf( "Buiding File List done\n" );

   printf( "Pruning\n" );
   PruneDatabase();
   printf( "Pruning done\n" );
}

int ScanForAudio::CountFiles( const QString& directory )
{
   QDir d(directory);
   QString path;
   int count = 0;

   if( !d.exists() )
   {
      return 0;
   }

   const QFileInfoList *list = d.entryInfoList();
   if( !list )
   {
      return 0;
   }

   QFileInfoListIterator it(*list);
   QFileInfo *fi;

   /* Recursively traverse directory, calling QApplication::processEvents()
      every now and then to ensure the UI updates */
   while( (fi = it.current()) != 0 )
   {
      ++it;
      if( fi->fileName() == "." || fi->fileName() == ".." )
      {
         continue;
      }
      QString filename = fi->absFilePath();
      if( fi->isDir() )
      {
         count += CountFiles( filename );
      }
      else
      {
         count++;
      }
   }

   return count;
}

void ScanForAudio::BuildFileList( const QString& directory )
{
   QDir d(directory);
   QString path;
   QString signature;
   QFileInfo*  fi;
   int update_interval = 0;

   if( !d.exists() )
   {
      return;
   }

   const QFileInfoList *list = d.entryInfoList();
   if( !list )
   {
      return;
   }

   QFileInfoListIterator it(*list);

   /* Recursively traverse directory, calling QApplication::processEvents()
      every now and then to ensure the UI updates */
   while( (fi = it.current()) != 0 )
   {
      ++it;
      if( fi->fileName() == "." || fi->fileName() == ".." )
      {
         continue;
      }
      QString filename = fi->absFilePath();
      if( fi->isDir() )
      {
         BuildFileList( filename );
         qApp->processEvents();
      }
      else
      {
         if( ++update_interval > 100 )
         {
            qApp->processEvents();
            update_interval = 0;
         }

         path = filename.remove( 0, RootPath.length() );
         if( Decoder::supports( path ) )
         {
            signature = ComputeFileSignature( path );
            if( Test( path, signature ) )
            {
               // File found in database, check tags
               //printf( "FOUND: %s\n", path.ascii() );
            }
            else
            {
               // File not in database
               //printf( "ADD: %s\n", path.ascii() );
               Add( path, signature );
            }
         }
         else
         {
            // File type not supported.
            // Check extention for other things like playlists
         }
      }
   }
}

void ScanForAudio::PruneDatabase()
{
   QString filename;
   QString artist;
   QString album;
   QString title;
   int year;
   int tracknum;
   FILE* f;
   QString path;
   unsigned char signature[ 16 ];

   MSqlQuery query(MSqlQuery::InitCon());
   MSqlQuery deleteQuery(MSqlQuery::InitCon());

   query.prepare( "SELECT filename, artist, album, title, year, tracknum from audio_tracks;" );
   query.exec();
   if( query.isActive() && query.size() > 0 )
   {
      while( query.next() )
      {
         filename = query.value(0).toString();
         artist   = query.value(1).toString();
         album    = query.value(2).toString();
         title    = query.value(3).toString();
         year     = query.value(4).toInt();
         tracknum = query.value(5).toInt();

         path = RootPath+filename;
         f = fopen( path.local8Bit(), "r" );
         if( f )
         {
            // File exists, compute signature
            md5_stream( f, &signature );
            fclose( f );
         }
         else
         {
printf( "DELETE %s\n", path.ascii() );
            deleteQuery.prepare( "DELETE from audio_tracks where filename=:FILENAME;" );
            deleteQuery.bindValue( ":FILENAME", filename );
            deleteQuery.exec();
         }
      }
   }
}

void ScanForAudio::CreateDatabase()
{
   QString queryString =
      "CREATE TABLE IF NOT EXISTS audio_tracks ("
      "    artist VARCHAR(128),"
      "    artist_search VARCHAR(128),"
      "    album VARCHAR(128),"
      "    album_search VARCHAR(128),"
      "    title VARCHAR(128),"
      "    title_search VARCHAR(128),"
      "    year INT UNSIGNED,"
      "    tracknum INT UNSIGNED,"
      "    filename TEXT NOT NULL,"
      "    filename_search TEXT NOT NULL,"
      "    signature VARCHAR(32)"
      ")"
      "    character set utf8";

   MSqlQuery query(MSqlQuery::InitCon());

   if( !query.exec( queryString ) )
   {
      QSqlError error = query.lastError();
      printf( "SQL Create Error\n" );
      printf( "driver: %s\n", error.driverText().ascii() );
      printf( "database: %s\n", error.databaseText().ascii() );
   }
}

bool ScanForAudio::Test( const QString& filename, const QString& signature )
{
   MSqlQuery query(MSqlQuery::InitCon());

   query.prepare( "SELECT filename from audio_tracks where filename=:FILE;" );
   query.bindValue( ":FILE", filename );
   if( !query.exec() )
   {
      QSqlError error = query.lastError();
      printf( "SQL SELECT Error\n" );
      printf( "driver: %s\n", error.driverText().ascii() );
      printf( "database: %s\n", error.databaseText().ascii() );
   }

   return query.next();
}

void ScanForAudio::Add( const QString& filename, const QString& signature )
{
   Decoder *decoder;
   QString artist;
   QString artist_search;
   QString album;
   QString album_search;
   QString title;
   QString title_search;
   QString filename_search;

   decoder = Decoder::create( RootPath+filename, NULL, NULL, true );
   if( decoder )
   {
      Metadata *data = decoder->getMetadata();
      if( data )
      {
         MSqlQuery query(MSqlQuery::InitCon());

         artist = (data->Artist().isEmpty() ? m_MetaUnknown : data->Artist());
         album  = (data->Album() .isEmpty() ? m_MetaUnknown : data->Album());
         title  = (data->Title() .isEmpty() ? m_MetaUnknown : data->Title());

         artist_search = TextTransform.Simplify( artist );
         album_search = TextTransform.Simplify( album );
         title_search = TextTransform.Simplify( title );
         filename_search = TextTransform.Simplify( filename );
         query.prepare
            (
               "INSERT INTO audio_tracks "
               "("
               "   artist,"
               "   artist_search,"
               "   album,"
               "   album_search,"
               "   title,"
               "   title_search,"
               "   year,"
               "   tracknum,"
               "   filename,"
               "   filename_search,"
               "   signature"
               ")"
               " VALUES "
               "("
               "   :ARTIST,"
               "   :ARTIST_SEARCH,"
               "   :ALBUM,"
               "   :ALBUM_SEARCH,"
               "   :TITLE,"
               "   :TITLE_SEARCH,"
               "   :YEAR,"
               "   :TRACKNUM,"
               "   :FILENAME,"
               "   :FILENAME_SEARCH,"
               "   :SIGNATURE"
               ");"
            );
         query.bindValue( ":ARTIST", artist );
         query.bindValue( ":ARTIST_SEARCH", artist_search );
         query.bindValue( ":ALBUM", album );
         query.bindValue( ":ALBUM_SEARCH", album_search );
         query.bindValue( ":TITLE", data->Title() );
         query.bindValue( ":TITLE_SEARCH", title_search );
         query.bindValue( ":YEAR", data->Year() );
         query.bindValue( ":TRACKNUM", data->Track() );
         query.bindValue( ":FILENAME", filename );
         query.bindValue( ":FILENAME_SEARCH", filename_search );
         query.bindValue( ":SIGNATURE", signature );
         if( !query.exec() )
         {
            QSqlError error = query.lastError();
            printf( "SQL INSERT Error\n" );
            printf( "driver: %s\n", error.driverText().ascii() );
            printf( "database: %s\n", error.databaseText().ascii() );
            printf( "OOPS: %s\n", query.lastQuery().ascii() );
         }

         delete data;
      }
      else
      {
         printf( "ERROR: %s\n", filename.ascii() );
         printf( "ERROR: Failed to read metadata\n" );
      }

      delete decoder;
   }
}

QString ScanForAudio::ComputeFileSignature( const QString& filename )
{
   unsigned char buffer[ 16 ];
   QString signature;
   QString path = RootPath+filename;

   struct stat    fileStat;

   if( stat( path.local8Bit(), &fileStat ) == 0 )
   {
      uint64_t    size = fileStat.st_size;
      uint64_t    time = fileStat.st_mtime;
   
      signature.sprintf( "%016lx%016lx", size, time );
   }

//   FILE* f;
//   f = fopen( path.local8Bit(), "r" );
//   if( f )
//   {
//      // File exists, compute signature
//      md5_stream( f, buffer );
//      signature.sprintf( "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
//                         buffer[  0 ], buffer[  1 ], buffer[  2 ], buffer[  3 ],
//                         buffer[  4 ], buffer[  5 ], buffer[  6 ], buffer[  7 ],
//                         buffer[  8 ], buffer[  9 ], buffer[ 10 ], buffer[ 11 ],
//                         buffer[ 12 ], buffer[ 13 ], buffer[ 14 ], buffer[ 15 ]
//                       );
//      fclose( f );
//   }
//   else
//   {
//      printf( "Failed to open %s\n", path.ascii() );
//   }

   return signature;
}

void ScanForAudio::run()
{
   printf( "Scan thread started\n" );
   Scan();
   printf( "Scan thread done\n" );
}
